package com.adam.framework.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * BeanUtil provides some method to resolve the general reflect problem
 * 
 * @author Adam Zhao
 * @since framework 0.1 / December 23, 2007
 */
public final class BeanUtil {

	private Log log = LogFactory.getLog(getClass());

	private static BeanUtil beanUtil = new BeanUtil();

	private BeanUtil() {
	}

	/**
	 * Get a BeanUtil instance
	 * 
	 * @return a instance
	 */
	public static BeanUtil getInstance() {
		return beanUtil;
	}

	public void populate(Object bean, Map<?, ?> properties) {
		if (bean == null || properties == null)
			return;
		Iterator<?> iter = properties.keySet().iterator();
		while (iter.hasNext()) {
			String name = (String) iter.next();
			Object value = properties.get(name);
			Object targetBean = bean;
			int p = name.lastIndexOf(".");
			if (p > 0) {
				targetBean = getTargetBean(bean, name);
				name = name.substring(p + 1);
			}
			if (value.getClass().isArray()) {
				setProperty(targetBean, name, (String[]) value);
			} else {
				setProperty(targetBean, name, value);
			}
		}
	}

	private Object getTargetBean(Object bean, String name) {
		if (name == null || name.indexOf(".") < 0) {
			return bean;
		}
		String[] names = name.split("\\.");
		Object o = bean;
		for (int i = 0; i < names.length - 1; i++) {
			String n = names[i];
			o = getBeanGetter(o, n);
		}
		return o;
	}

	private Object getBeanGetter(Object bean, String name) {
		String targetMethodName = "get" + name.substring(0, 1).toUpperCase()
				+ (name.length() > 1 ? name.substring(1) : "");
		Method[] methods = bean.getClass().getMethods();
		Method getter = null;
		for (Method m : methods) {
			if (m.getName().equals(targetMethodName) && m.getParameterTypes().length == 0) {
				getter = m;
				break;
			}
		}
		if (getter == null) {
			return null;
		}
		Object o = null;
		try {
			o = getter.invoke(bean);
		} catch (IllegalArgumentException e) {
			log.warn(e);
		} catch (IllegalAccessException e) {
			log.warn(e);
		} catch (InvocationTargetException e) {
			log.warn(e);
		}
		return o;
	}

	private void setProperty(Object bean, String name, Object value) {
		if (bean == null || name == null || value == null)
			return;

		String targetMethodName = "set" + name.substring(0, 1).toUpperCase()
				+ (name.length() > 1 ? name.substring(1) : "");
		Method[] methods = bean.getClass().getMethods();
		for (Method m : methods) {
			if (m.getName().equals(targetMethodName) && m.getParameterTypes().length == 1) {
				try {
					Object newValue = convertParameterType(value, m.getParameterTypes()[0]);
					m.invoke(bean, newValue);
					break;
				} catch (IllegalArgumentException e) {
					log.warn(e);
				} catch (IllegalAccessException e) {
					log.warn(e);
				} catch (InvocationTargetException e) {
					log.warn(e);
				}
			}
		}
	}

	private void setProperty(Object bean, String name, Object[] values) {
		if (bean == null || name == null || values == null || values.length == 0)
			return;

		String targetMethodName = "set" + name.substring(0, 1).toUpperCase()
				+ (name.length() > 1 ? name.substring(1) : "");
		Method[] methods = bean.getClass().getMethods();
		for (Method m : methods) {
			if (m.getName().equals(targetMethodName) && m.getParameterTypes().length == 1) {
				try {
					Object newValue = convertArrayParameterType(values, m.getParameterTypes()[0]);
					m.invoke(bean, newValue);
					break;
				} catch (IllegalArgumentException e) {
					log.warn(e);
				} catch (IllegalAccessException e) {
					log.warn(e);
				} catch (InvocationTargetException e) {
					log.warn(e);
				}
			}
		}
	}

	private Object convertParameterType(Object value, Class<?> type) {
		if (type.isArray()) {
			Object[] arrayValue = new Object[0];
			arrayValue[0] = value;
			return this.convertArrayParameterType(arrayValue, type);
		}
		String strValue = (String) value;
		if (type == String.class) {
			return String.valueOf(value);
		} else if (type == int.class || type == Integer.class) {
			return Integer.valueOf(strValue);
		} else if (type == long.class || type == Long.class) {
			return Long.parseLong(strValue);
		} else if (type == float.class || type == Float.class) {
			return Float.parseFloat(strValue);
		} else if (type == boolean.class || type == Boolean.class) {
			return Boolean.parseBoolean(strValue);
		} else if (type == double.class || type == Double.class) {
			return Double.parseDouble(strValue);
		} else if (type == byte.class || type == Byte.class) {
			return Byte.parseByte(strValue);
		} else if (type == short.class || type == Short.class) {
			return Short.parseShort(strValue);
		} else if ((type == char.class || type == Character.class) && strValue.length() > 0) {
			return strValue.charAt(0);
		} else {
			throw new IllegalArgumentException("Cannot convert to type: " + type.getName());
		}
	}

	private Object convertArrayParameterType(Object[] values, Class<?> type) {
		if (!type.isArray()) {
			return this.convertParameterType(values[0], type);
		}
		String[] strArray = (String[]) values;
		if (type == String[].class) {
			return values;
		} else if (type == int[].class) {
			int[] intArray = new int[strArray.length];
			for (int i = 0; i < intArray.length; i++)
				intArray[i] = Integer.parseInt(strArray[i]);
			return intArray;
		} else if (type == Integer[].class) {
			Integer[] array = new Integer[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Integer.parseInt(strArray[i]);
			return array;

		} else if (type == long[].class) {
			long[] longArray = new long[strArray.length];
			for (int i = 0; i < longArray.length; i++)
				longArray[i] = Long.parseLong(strArray[i]);
			return longArray;

		} else if (type == Long[].class) {
			Long[] array = new Long[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Long.parseLong(strArray[i]);
			return array;

		} else if (type == double[].class) {
			double[] array = new double[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Double.parseDouble(strArray[i]);
			return array;

		} else if (type == Double[].class) {
			Double[] array = new Double[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Double.parseDouble(strArray[i]);
			return array;

		} else if (type == float[].class) {
			float[] array = new float[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Float.parseFloat(strArray[i]);
			return array;

		} else if (type == Float[].class) {
			Float[] array = new Float[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Float.parseFloat(strArray[i]);
			return array;

		} else if (type == byte[].class || type == Byte[].class) {
			byte[] array = new byte[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Byte.parseByte(strArray[i]);
			return array;

		} else if (type == Byte[].class) {
			Byte[] array = new Byte[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Byte.parseByte(strArray[i]);
			return array;

		} else if (type == short[].class) {
			short[] array = new short[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Short.parseShort(strArray[i]);
			return array;

		} else if (type == short[].class) {
			Short[] array = new Short[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Short.parseShort(strArray[i]);
			return array;

		} else if (type == char[].class) {
			char[] array = new char[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = strArray[i].charAt(0);
			return array;

		} else if (type == Character[].class) {
			char[] array = new char[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = strArray[i].charAt(0);
			return array;

		} else if (type == boolean[].class) {
			boolean[] array = new boolean[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Boolean.parseBoolean(strArray[i]);
			return array;

		} else if (type == Boolean[].class) {
			Boolean[] array = new Boolean[strArray.length];
			for (int i = 0; i < array.length; i++)
				array[i] = Boolean.parseBoolean(strArray[i]);
			return array;

		} else {
			throw new IllegalArgumentException("Cannot convert to type: " + type.getName());
		}

	}
}
